using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using System.Data.SQLite;
using System.IO;
using System.Threading;
using MacomberMap.Common.Database_Interface.CIM;
using MacomberMap.Common.Database_Interface.CIM.Components;
using MacomberMap.Common.Internals;

namespace MacomberMap.Loader.Export.NOMCRs
{
    /// <summary>
    /// This form displays information on the rules
    /// </summary>
    public partial class frmNOMCRGenerator : Form
    {
        #region Variable declarations
        /// <summary>The path to the database of existing enhancements</summary>        
        public String DatabasePath;

        /// <summary>The output path for the NOMCRs</summary>
        public String OutputPath;

        /// <summary>The current rule being executed</summary>
        private int CurRule = 0;

        /// <summary>The collection of OTS rules</summary>
        private List<MM_OTS_Rule> OTSRules = new List<MM_OTS_Rule>();

        /// <summary>The SQLite connection</summary>
        private SQLiteConnection sConn;

        /// <summary>Our menu for our items</summary>
        private ContextMenuStrip cms = new ContextMenuStrip();

        /// <summary>The CIM database</summary>
        public MM_Database_CIM CIMDb;

        /// <summary>Our collection of exceptions</summary>
        public List<MM_OTS_Rule_Exception> Exceptions = new List<MM_OTS_Rule_Exception>();

        /// <summary>Our table of NOMCRs</summary>
        private DataTable NOMCRs;
        #endregion

        #region Initialization
        /// <summary>
        /// Initialize the NOMCR generator
        /// </summary>
        public frmNOMCRGenerator(String SQLitePath, MM_Database_CIM CIMDb)
        {
            InitializeComponent();
            this.CIMDb = CIMDb;
            cms.ItemClicked += new ToolStripItemClickedEventHandler(cms_ItemClicked);

            bool AddTable = !File.Exists(SQLitePath);
            sConn = new SQLiteConnection("Data Source=" + SQLitePath);
            sConn.Open();
            if (AddTable)
            {
                using (SQLiteCommand sCmd = new SQLiteCommand("CREATE TABLE NOMCRs ([UID] INTEGER PRIMARY KEY AUTOINCREMENT, [TEID] BIGINT UNSIGNED, [rdfID] VARCHAR UNIQUE, [Rule] VARCHAR, [XML] VARCHAR, [CreatedOn] TIMESTAMP DEFAULT CURRENT_TIMESTAMP)", sConn))
                    sCmd.ExecuteNonQuery();
                using (SQLiteCommand sCmd = new SQLiteCommand("CREATE TABLE Rules ([UID] INTEGER PRIMARY KEY AUTOINCREMENT, [Name] VARCHAR, [RuleType] INTEGER, [ElementType] VARCHAR, [Attributes] VARCHAR, [ComparisonParameter] VARCHAR, [Constraint_Value] VARCHAR)", sConn))
                    sCmd.ExecuteNonQuery();
            }

            using (SQLiteCommand sCmd = new SQLiteCommand("SELECT * FROM Rules", sConn))
            using (SQLiteDataReader sRd = sCmd.ExecuteReader())
                while (sRd.Read())
                    OTSRules.Add(new MM_OTS_Rule(sRd, lvRules));
            lvRules.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
            lvRules.Columns[0].AutoResize(ColumnHeaderAutoResizeStyle.HeaderSize);
            lvRules.Sorting = SortOrder.Ascending;
            lvRules.Sort();

            BindingSource bSource = new BindingSource();            
            NOMCRs = new DataTable();
            
            NOMCRs.Locale = System.Globalization.CultureInfo.InvariantCulture;
            bSource.DataSource = NOMCRs;

            using (SQLiteDataAdapter sAdap = new SQLiteDataAdapter("SELECT * FROM NOMCRs", sConn))
                sAdap.Fill(NOMCRs);
            dgvNOMCRs.DataSource = bSource;
        }
        #endregion

        #region Rules creation/editing
        /// <summary>
        /// Update the index of the rule
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lvRules_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (lvRules.SelectedItems.Count != 1)
                pgRule.Visible = false;
            else
            {
                pgRule.Visible = true;
                pgRule.SelectedObject = lvRules.SelectedItems[0].Tag;
            }
        }

        /// <summary>
        /// Handle the user right-clicking on the rules
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lvRules_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button != MouseButtons.Right)
                return;
            ListViewItem Item = lvRules.HitTest(e.Location).Item;
            cms.Items.Clear();
            if (Item == null)
            {
                cms.Tag = null;
                cms.Items.Add("&Add new NOMCR rule...");
                cms.Items.Add("&Execute NOMCR rules");
                cms.Items.Add("&Save NOMCR rules");
                cms.Items.Add("So&rt NOMCR rules");
            }
            else
            {
                MM_OTS_Rule Rule = Item.Tag as MM_OTS_Rule;
                cms.Items.Add("&Clone NOMCR rule " + Rule.Name);
                cms.Items.Add("&Delete NOMCR rule " + Rule.Name);
                cms.Tag = Rule;
            }
            cms.Show(sender as Control, e.Location);
        }

        /// <summary>
        /// Handle the user right-clicking on the rules
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void tvRules_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button != MouseButtons.Right)
                return;
            TreeNode FoundNode = tvRules.HitTest(e.Location).Node;
            if (FoundNode == null)
                return;
            tvRules.SelectedNode = FoundNode;
            MM_OTS_Rule_Exception Exception = FoundNode.Tag as MM_OTS_Rule_Exception;
            if (Exception == null)
                return;
            cms.Items.Clear();
            cms.Items.Add(Exception.BaseRule.Name).Enabled = false;
            if (Exception.ExceptionElement.VoltageLevel != null)
                cms.Items.Add(Exception.ExceptionElement.Type.Split(':')[1] + " " + Exception.ExceptionElement.Name + " (" + Exception.ExceptionElement.VoltageLevel.Name + ")").Enabled = false;
            else
                cms.Items.Add(Exception.ExceptionElement.Type.Split(':')[1] + " " + Exception.ExceptionElement.Name).Enabled = false;
            cms.Items.Add("-");
            cms.Items.Add("CIM Browser").Tag = Exception.ExceptionElement;
            cms.Items.Add("-");

            if (Exception.BaseRule.RuleType == MM_OTS_Rule.enumRuleType.A_MustHave_B || Exception.BaseRule.RuleType == MM_OTS_Rule.enumRuleType.Substation_Must_Have)
            {
                cms.Items.Add("Create new " + Exception.BaseRule.ComparisonParameter);
                String TargetElem = Exception.BaseRule.ComparisonParameter.Split('>')[0];
                int RequiredElements = 1;
                int.TryParse(Exception.BaseRule.Constraint_Value, out RequiredElements);

                foreach (CIM_Element Link in Exception.ExceptionElement.Links)
                    foreach (CIM_Element Link2 in Link.Links)
                        if (Link2.Type == TargetElem && Link2.ParseMultiples(Exception.BaseRule.ComparisonParameter.Substring(Exception.BaseRule.ComparisonParameter.IndexOf('>')+1)).Length >= RequiredElements)
                            if (Link2.VoltageLevel != null)
                                cms.Items.Add("Link to existing " + Link2.Type.Split(':')[1] + " " + Link2.Name + " (" + Link2.VoltageLevel.Name + ")").Tag = Link2;
                            else
                                cms.Items.Add("Link to existing " + Link2.Type.Split(':')[1] + " " + Link2.Name).Tag = Link2;


            }
            else
                cms.Items.Add("Rename " + Exception.BaseRule.ComparisonParameter + " from " + Exception.ExceptionElement[Exception.BaseRule.ComparisonParameter]);
            cms.Tag = Exception;
            cms.Show(tvRules,e.Location);
                
        }


        /// <summary>
        /// Update a NOMCR rule when it changes
        /// </summary>
        /// <param name="s"></param>
        /// <param name="e"></param>
        private void pgRule_PropertyValueChanged(object s, PropertyValueChangedEventArgs e)
        {
            (pgRule.SelectedObject as MM_OTS_Rule).UpdateValues();
            lvRules.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
        }

        /// <summary>
        /// Handle a menu strip item click
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cms_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {
            if (e.ClickedItem.Text.StartsWith("&Add new NOMCR rule"))
            {
                MM_OTS_Rule NewRule = new MM_OTS_Rule("New Rule 1", lvRules);
                OTSRules.Add(NewRule);
                lvRules.SelectedItems.Clear();
                NewRule.lvI.Selected = true;
            }
            else if (e.ClickedItem.Text == "So&rt NOMCR rules")
            {
                OTSRules.Clear();
                lvRules.Sorting = SortOrder.None;
                lvRules.Sorting = SortOrder.Ascending;
                lvRules.Sort();
                foreach (ListViewItem lvI in lvRules.Items)
                    OTSRules.Add(lvI.Tag as MM_OTS_Rule);
            }
            else if (e.ClickedItem.Text == "&Save NOMCR rules")
            {
                foreach (MM_OTS_Rule Rule in this.OTSRules)
                    Rule.WriteDatabase(sConn, null, "Rules");
            }
            else if (e.ClickedItem.Text.StartsWith("&Clone NOMCR rule"))
            {
                MM_OTS_Rule NewRule = new MM_OTS_Rule(cms.Tag as MM_OTS_Rule, lvRules);
                OTSRules.Add(NewRule);
                lvRules.SelectedItems.Clear();
                NewRule.lvI.Selected = true;
            }
            else if (e.ClickedItem.Text.StartsWith("&Delete NOMCR rule"))
            {
                MM_OTS_Rule Rule = cms.Tag as MM_OTS_Rule;
                OTSRules.Remove(Rule);
                lvRules.Items.Remove(Rule.lvI);
            }
            else if (e.ClickedItem.Text.StartsWith("&Execute NOMCR rules"))
            {
                tabMain.SelectedTab = tabExecution;
                tvRules.Nodes.Clear();
                ThreadPool.QueueUserWorkItem(new WaitCallback(ParseRules));
            }
            else if (e.ClickedItem.Text == "CIM Browser")
                new MacomberMap.Common.Database_Interface.CIM.User_Interfaces.frmCIMBrowser().Show(e.ClickedItem.Tag as CIM_Element);
            else if (e.ClickedItem.Text.StartsWith("Link to existing"))
            {
                MM_OTS_Rule_Exception Exception = cms.Tag as MM_OTS_Rule_Exception;

            }
            else if (e.ClickedItem.Text.StartsWith("Rename"))
            {
                MM_OTS_Rule_Exception Exception = cms.Tag as MM_OTS_Rule_Exception;
                String CurValue = Exception.ExceptionElement[Exception.BaseRule.ComparisonParameter];
                String OutStr = Exception.BaseRule.Name + ":\nPlease select a new value for " + Exception.BaseRule.ComparisonParameter + "\n(old value " + CurValue + "):";
                if (!String.IsNullOrEmpty(Exception.BaseRule.Constraint_Value))
                    CurValue = CurValue.Substring(0, int.Parse(Exception.BaseRule.Constraint_Value));

                if (MM_InputBox.InputBox(Application.ProductName, OutStr, ref CurValue) == DialogResult.OK)
                {
                    DataRow[] FoundRows = NOMCRs.Select("[rdfID]='" + Exception.ExceptionElement.rdfID.ToString() + "'");
                    bool ModifyExisting = FoundRows.Length > 0;

                    DataRow NewRow = NOMCRs.NewRow();
                    NewRow["TEID"] = Exception.ExceptionElement.TEID;
                    NewRow["rdfID"] = Exception.ExceptionElement.rdfID.ToString();

                    if (ModifyExisting)
                    {
                        StringBuilder sBd = new StringBuilder();
                        String inLine;
                        using (StringReader sRd = new StringReader((string)FoundRows[0]["XML"]))
                            while (!String.IsNullOrEmpty(inLine = sRd.ReadLine()))
                                if (inLine.StartsWith(Exception.BaseRule.ComparisonParameter, StringComparison.CurrentCultureIgnoreCase))
                                    sBd.AppendLine(Exception.BaseRule.ComparisonParameter + "=" + CurValue);
                                else
                                    sBd.AppendLine(inLine);
                        if (Exception.BaseRule.Name == (string)FoundRows[0]["Rule"])
                            NewRow["Rule"] = Exception.BaseRule.Name;
                        else
                            NewRow["Rule"] = (string)FoundRows[0]["Rule"] + "\r" + Exception.BaseRule.Name;
                    }
                    else
                    {
                        NewRow["XML"] = Exception.BaseRule.ComparisonParameter + "=" + CurValue;
                        NewRow["Rule"] = Exception.BaseRule.Name;
                    }


                    StringBuilder sB = new StringBuilder();
                    foreach (MM_OTS_Rule Rule in OTSRules)
                        if (!Rule.TestNOMCR(NewRow, Exception))
                            sB.AppendLine(Rule.Name);

                    if (sB.Length > 0)
                    {
                        MessageBox.Show("This proposed change failed rule validation:\n" + sB.ToString(), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
                        NewRow.Delete();
                        return;
                    }

                    if (ModifyExisting)
                        using (SQLiteCommand sCmd = new SQLiteCommand("UPDATE NOMCRs SET [XML]=:XML, [Rule]=:Rule WHERE rdfID=:rdfID", sConn))
                        {
                            sCmd.Prepare();
                            sCmd.Parameters.AddWithValue("XML", NewRow["XML"]);
                            sCmd.Parameters.AddWithValue("Rule", NewRow["Rule"]);
                            sCmd.Parameters.AddWithValue("rdfID", NewRow["rdfID"]);
                            sCmd.ExecuteNonQuery();
                        }
                    else
                        using (SQLiteCommand sCmd = new SQLiteCommand("INSERT INTO NOMCRs ([TEID], [rdfID], [Rule],[XML]) VALUES (:TEID, :rdfID, :Rule, :XML); SELECT last_insert_rowid() AS [UpdatedIndex]", sConn))
                        {
                            sCmd.Prepare();
                            foreach (String str in "TEID,rdfID,Rule,XML".Split(','))
                                sCmd.Parameters.AddWithValue(str, NewRow[str]);
                            NewRow["UID"] = sCmd.ExecuteScalar();
                            NewRow["CreatedOn"] = DateTime.Now;
                            NOMCRs.Rows.Add(NewRow);

                            //Now, process this NOMCR across all exceptions
                            foreach (MM_OTS_Rule_Exception TestException in Exceptions)
                                if (TestException.ExceptionElement == Exception.ExceptionElement)
                                {
                                    TestException.BaseRule.ConfirmNOMCR(NewRow);
                                    TestException.AssignRow(NewRow);
                                }
                            foreach (MM_OTS_Rule Rule in OTSRules)
                                Rule.UpdateNode(Exceptions);

                        }
                }
            }
        }

        /// <summary>
        /// Go through all NOMCR rules and parse them accordingly
        /// </summary>
        /// <param name="State"></param>
        private void ParseRules(object State)
        {
            
            //First, build our dictionary of elements to be parsed            
            Dictionary<MM_OTS_Rule, List<CIM_Element>> RulesToParse = new Dictionary<MM_OTS_Rule, List<CIM_Element>>();
            int ElementCount = 0;

            OTSRules.Sort();
            foreach (MM_OTS_Rule Rule in OTSRules)
                RulesToParse.Add(Rule, Rule.RetrieveElements(ref ElementCount));

            CurRule = 0;            
           
            pgProgress.Maximum = ElementCount;
            


            foreach (KeyValuePair<MM_OTS_Rule, List<CIM_Element>> kvp in RulesToParse)
            {
                kvp.Key.BaseNode = tvRules.Invoke(new SafeCreateNode(CreateNode), tvRules.Nodes, kvp.Key) as TreeNode;
                kvp.Key.ProcessRule(CIMDb, sConn, Exceptions, tvRules, NOMCRs, kvp.Value, ref CurRule);                
            }

            foreach (MM_OTS_Rule Rule in RulesToParse.Keys)
                Rule.UpdateNode(Exceptions);
        }

        private delegate TreeNode SafeCreateNode(TreeNodeCollection tnColl, Object AssignedObject);

        /// <summary>
        /// Create a new node for a rule or exception
        /// </summary>
        /// <param name="tnColl"></param>
        /// <param name="AssignedObject"></param>
        /// <returns></returns>
        private TreeNode CreateNode(TreeNodeCollection tnColl, Object AssignedObject)
        {
            TreeNode OutNode = tnColl.Add((AssignedObject as MM_OTS_Rule).Name);            
            OutNode.Tag = AssignedObject;
            return OutNode;
        }
        #endregion

        /// <summary>
        /// Every second, update our status
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void tmrUpdate_Tick(object sender, EventArgs e)
        {
            try
            {
                if (CurRule == 0 || CurRule == pgProgress.Maximum)
                    pgProgress.Visible = false;
                else
                {
                    pgProgress.Visible = true;
                    pgProgress.Value = CurRule;
                }
            }
            catch (Exception ex)
            {
                File.WriteAllText("MMLoader-TickUpdateError-" + DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss") + ".log", ex.ToString());
            }
        }

        /// <summary>
        /// Save the NOMCR rules
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSAveNOMCRs_Click(object sender, EventArgs e)
        {
            foreach (MM_OTS_Rule Rule in this.OTSRules)
                Rule.WriteDatabase(sConn, null, "Rules");    
        }

        /// <summary>
        /// Execute the NOMCR rules
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnExecuteNOMCRs_Click(object sender, EventArgs e)
        {
            tabMain.SelectedTab = tabExecution;
            tvRules.Nodes.Clear();
            ThreadPool.QueueUserWorkItem(new WaitCallback(ParseRules));
        }
    }
}